/**
 * \file ActiveObject.h
 * \brief ActiveObject definition and declaration class
 * \author Edouard ROGE
 * \date 04 May 2014
 */

#ifndef _ACTIVEOBJECT_
#define _ACTIVEOBJECT_

#include <mutex>
#include <condition_variable>
#include <queue>
#include <memory>
#include <thread>
#include "Core/Define/Define.h"

namespace Core
{
	class ActiveObject;
	typedef std::unique_ptr<ActiveObject> ActiveObjectUPtr;

	/**
	 * \class ActiveObject
	 * \brief Base class to create an active object.
	 */
	class ActiveObject
	{
	public:

		/**
		 * \fn ~ActiveObject();
		 * \brief Destructor
		 */
		~ActiveObject();

		/**
		 * \fn static ActiveObjectUPtr CreateActiveObject();
		 * \brief Create a new active object
		 */
		static ActiveObjectUPtr CreateActiveObject();

		/**
		 * \fn void Send(VoidFunctionVoid _message);
		 * \param _message Message to queue
		 * \brief Queue the message in parameter
		 */
		void Send(VoidFunctionVoid _message);

		void Synchronize();

	private:

		template<typename T>
		class SharedQueue
		{
		public:
			void Push(T _item)
			{
				{
					std::lock_guard<std::mutex> lock(m_Mutex);
					m_Queue.push(_item);
				}
				m_ConditionVariable.notify_one();
			}

			/// \return immediately, with true if successful retrieval
			bool Pop(T & _item)
			{
				std::lock_guard<std::mutex> lock(m_Mutex);

				if (m_Queue.empty())
				{
					return false;
				}

				_item = std::move(m_Queue.front());
				m_Queue.pop();
				return true;
			}

			/// Try to retrieve, if no items, wait till an item is available and try again
			void WaitAndPop(T & _item)
			{
				std::unique_lock<std::mutex> lock(m_Mutex); // note: unique_lock is needed for std::condition_variable::wait
				while (m_Queue.empty())
				{ //                       The 'while' loop below is equal to
					m_ConditionVariable.wait(lock);  //data_cond_.wait(lock, [](bool result){return !queue_.empty();});
				}
				_item = std::move(m_Queue.front());
				m_Queue.pop();
			}

			bool Empty() const
			{
				std::lock_guard<std::mutex> lock(m_Mutex);
				return m_Queue.empty();
			}

			unsigned Size() const
			{
				std::lock_guard<std::mutex> lock(m_Mutex);
				return m_Queue.size();
			}

		private:
			std::queue<T> m_Queue;
			mutable std::mutex m_Mutex;
			std::condition_variable m_ConditionVariable;
		};

		/**
		* \fn ActiveObject();
		* \brief Constructor
		*/
		ActiveObject();

		/**
		* \fn ActiveObject();
		* \brief Constructor
		*/
		ActiveObject(const ActiveObject & _other);

		/**
		* \fn ActiveObject();
		* \brief Constructor
		*/
		ActiveObject & operator=(const ActiveObject & _other);

		/**
		* \fn void Run();
		* \brief Function that dequeue messages and perform them in a different thread
		*/
		void Run();

		/**
		* \fn void Done();
		* \brief Function that tells the thread to finish
		*/
		void Done();

		/** thread that dequeues messages */
		std::thread m_thread;

		/** Queue to store messages and perform them */
		SharedQueue<VoidFunctionVoid> m_MessageQueue;

		/** Flag that indicates if the job is done */
		bool m_bDone;
	};
}

#endif // _ACTIVEOBJECT_
